﻿using System;
using System.Windows.Markup;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Documents;
using System.Xml.Linq;
using System.Linq;
using System.Collections.Generic;

namespace Microsoft.Popfly.GameCreator.GameEngine
{
    public static class Utils
    {
        public static string ErrorMessagePrefix = 
            "Oops, we made a goof.  Please visit our forums at " + 
            "http://forums.microsoft.com/msdn/ShowForum.aspx?ForumID=2125&SiteID=1 " + 
            "and let us know so we can help you.  When you post, " + 
            "it will help us track down the problem if share a link " + 
            "to your game (if appropriate), provide a description of the steps " + 
            "you took to get the error and paste in the following error message:  \n";

        // TODO: can we use Convert.ToDouble?
        public static double DoubleFromObject(Object obj)
        {
            if (obj == null)
            {
                return 0;
            }
            if (obj.GetType().Equals(typeof(string)))
            {
                if (((string)obj).Equals(""))
                {
                    return 0;
                }
                return Double.Parse((string)obj);
            }
            else
            {
                return (double) obj;
            }
        }

        public static double ValidDoubleFromObject(Object obj)
        {
            double doubleValue = DoubleFromObject(obj);
            if (Double.IsNaN(doubleValue))
            {
                throw new ArgumentException("The property you are setting does not accept NaN as a valid value");
            }
            else if (Double.IsInfinity(doubleValue))
            {
                throw new ArgumentException("This property you are setting does not accept +/- infinity as a valid value");
            }
            else
            {
                return doubleValue;
            }
        }
        public static int IntFromObject(Object obj)
        {
            if (obj.GetType().Equals(typeof(string)))
            {
                return Int32.Parse((string)obj);
            }
            else if (obj is double)
            {
                return (int)Math.Round((double)obj);
            }
            else
            {
                return (int)obj;
            }
        }

        public static bool BoolFromObject(Object obj)
        {
            if (obj.GetType().Equals(typeof(string)))
            {
                return Boolean.Parse((string)obj);
            }
            else
            {
                return (bool)obj;
            }
        }

        public static string KeyFromResourceAttribute(string str)
        {
            var retVal = str.Trim(new char[] { '{', '}' });
            // Remove the leading "StaticReference"
            retVal = retVal.Remove(0, 15);
            return retVal;
        }

        public static bool Then(this bool condition, Action action)
        {
            if (action == null)
                throw new ArgumentNullException("action");

            if (condition)
                action.Invoke();

            return condition;
        }

        // Fixes up any quirks (eg, SL1 javascript to SL2 managed code differences in XAML parsing)

        private static List<String> m_mediaEndedElementsToHook = new List<String>();
        public static Canvas CreateFromXaml(string xaml)
        {	
			//Try to load the xaml
			Canvas canvas = null;
			try
			{
				m_mediaEndedElementsToHook.Clear();

				// parse the xaml
				XElement root = XElement.Parse(xaml);

				// fixup xaml issues
				CreateFromXamlHelper(root, -1, -1, null);


				canvas = XamlReader.Load(root.ToString()) as Canvas;

				if (canvas == null)
				{
					//Xaml not wrapped in canvas, so do that now
					canvas = XamlReader.Load("<Canvas xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">" + root.ToString() + "</Canvas>") as Canvas;
				}
			}
			catch(Exception exception)
			{
				Console.ReportError("Error creating xaml.\n" +
                    "Message: '" + exception.Message + "'\n" +
				    "Exception: \n" + exception.ToString());

				//Xaml was invalid so replace with the missing actor xaml
				canvas = new BadActor();
			}

            // add back mediaended handlers
            foreach (string name in m_mediaEndedElementsToHook)
            {
                MediaElement me = canvas.FindName(name) as MediaElement;
                me.MediaEnded += new RoutedEventHandler(PopflyXAMLReplayHandler);
            }

            return canvas;
        }

        private static void PopflyXAMLReplayHandler(object sender, RoutedEventArgs e)
        {
            ((MediaElement)sender).Stop();
            ((MediaElement)sender).Play();
        }

        private static void SwapAttributeName(XElement element, string oldName, string newName)
        {
            XAttribute attribute = element.Attribute(oldName);
            if (attribute != null)
            {
                element.SetAttributeValue(newName, attribute.Value);
                element.SetAttributeValue(oldName, null);
            }
        }

        private static void CreateFromXamlHelper(XElement root, double latestWidth, double latestHeight, string latestRenderTransformOrigin)
        {
            var elements = root.DescendantsAndSelf();
            foreach (XElement element in elements)
            {
                // Make RenderTransformOrigin work the way it did in SL1.0 Javascript
                XAttribute width = element.Attribute("Width");
                XAttribute height = element.Attribute("Height");
                XAttribute renderTransformOrigin = element.Attribute("RenderTransformOrigin");
                if (width != null)
                {
                    double tryWidth;
                    if (Double.TryParse(width.Value, out tryWidth))
                    {
                        latestWidth = tryWidth;
                    }
                }
                if (height != null)
                {
                    double tryHeight;
                    if (Double.TryParse(height.Value, out tryHeight))
                    {
                        latestHeight = tryHeight;
                    }
                }
                if (element.Name.LocalName == "Image" && renderTransformOrigin != null)
                {
                    latestRenderTransformOrigin = renderTransformOrigin.Value;
                }
                FixupXamlElement(element, latestWidth, latestHeight, latestRenderTransformOrigin);
            }
        }

        // TODO: this was to make SL2 managed code behave like SL1 javascript for fonts.  It's probably not necessary
        // if you're not worried about backwards compat with the Popfly designer.
        private static void FixupXamlElement(XElement element, double latestWidth, double latestHeight, string latestRenderTransformOrigin)
        {
            if (element.Name.LocalName == "Glyphs")
            {
                // change glyphs to textblocks
                element.Name = element.Name.Namespace + "TextBlock";
                SwapAttributeName(element, "UnicodeString", "Text");               
                SwapAttributeName(element, "Fill", "Foreground");
                SwapAttributeName(element, "FontRenderingEmSize", "FontSize");
                SwapAttributeName(element, "FontUri", "FontFamily"); // note glyphs->textblocks will have no "#family", handle when setting source

                // update child nodes
                foreach (var el in element.Elements())
                {
                    if (el.Name.LocalName == "Glyphs.Fill")
                    {
                        el.Name = el.Name.Namespace + "TextBlock.Foreground";
                    }
                    else if (el.Name.LocalName.StartsWith("Glyphs."))
                    {
                        el.Name = el.Name.Namespace + el.Name.LocalName.Replace("Glyphs.", "TextBlock.");
                    }
                }
            }
            else if (element.Name.LocalName == "TextBlock")
            {
                // change default font to Lucida Sans Unicode (as it was in SL1)
                XAttribute attrib = element.Attribute("FontFamily");
                
                if (attrib == null)
                {
                    // Another SL1->SL2 compat hack to keep the fonts the same as they were in SL1 javascript
                    // no fontfamily attribute, add one
                    attrib = new XAttribute(element.Name.Namespace + "FontFamily", "Lucida Sans Unicode");
                    element.Add(attrib);
                }
                else if(attrib.Value == "" || attrib.Value == "Portable User Interface" || attrib.Value == "Lucida Grande")
                {
                    attrib.Value = "Lucida Sans Unicode";
                }
            }
            else if (element.Name.LocalName == "MediaElement")
            {
                // can't CreateFromXaml for xaml with events
                XAttribute attrib = element.Attribute("MediaEnded");
                if (attrib != null && attrib.Value == "Popfly_XAML_Replay")
                {
                    attrib.Remove();
                    XAttribute nameAttrib = null;
                    // find the Name attribute using localname if it exists
                    foreach (XAttribute attribute in element.Attributes())
                    {
                        if (attribute.Name.LocalName == "Name")
                        {
                            nameAttrib = attribute;
                            break;
                        }
                    }

                    if (nameAttrib == null)
                    {
                        // It doesn't exist, so add it
                        string name = "__PopflyReplayNameAttribute" + m_mediaEndedElementsToHook.Count;
                        element.SetAttributeValue("Name", name);
                        m_mediaEndedElementsToHook.Add(name);
                    }
                    else
                    {
                        m_mediaEndedElementsToHook.Add(nameAttrib.Value);
                    }
                }
            }
            else if (element.Parent != null &&
                     element.Parent.Name.LocalName == "Image.RenderTransform" && 
                     element.Name.LocalName == "RotateTransform" &&
                     latestWidth != -1 &&
                     latestHeight != -1 &&
                     latestRenderTransformOrigin != null &&
                     element.Attribute("CenterX") == null && 
                     element.Attribute("CenterY") == null
                )
            {
                string[] originVals = latestRenderTransformOrigin.Split(new char[] { ',' });
                if (originVals.Length == 2)
                {
                    double scaleX, scaleY;
                    if (double.TryParse(originVals[0].Trim(), out scaleX) && 
                        double.TryParse(originVals[1].Trim(), out scaleY))
                    {
                        element.SetAttributeValue("CenterX", (scaleX * latestWidth).ToString());
                        element.SetAttributeValue("CenterY", (scaleY * latestHeight).ToString());
                    }
                }
            }
        }
    }
}
